﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace CXW.Core
{
    /// <summary>
    /// Static class containing the AddInTree. Contains methods for accessing tree nodes and building items.
    /// </summary>
    public static class AddInTree
    {
        static List<AddIn> addIns = new List<AddIn>();
        static AddInTreeNode rootNode = new AddInTreeNode();

        static ConcurrentDictionary<string, IDoozer> doozers = new ConcurrentDictionary<string, IDoozer>();

        static AddInTree()
        {
            doozers.TryAdd("MenuItem", new MenuItemDoozer());
            doozers.TryAdd("ToolItem", new ToolbarItemDoozer());
            doozers.TryAdd("TreeItem", new ToolbarItemDoozer());
        }

        /// <summary>
        /// Gets the list of loaded AddIns.
        /// </summary>
        public static IList<AddIn> AddIns { get { return addIns.AsReadOnly(); } }

        public static void AddSystemCodonId(string codonId)
        {
            if (!sysCodon.ContainsKey(codonId))
            { sysCodon.Add(codonId, null); }
        }

        public static void AddEnableCodonId(string codonId)
        {
            if (!enableCodon.ContainsKey(codonId))
            { enableCodon.Add(codonId, null); }
        }

        static Dictionary<string, Codon> sysCodon = new Dictionary<string, Codon>(StringComparer.CurrentCultureIgnoreCase);
        /// <summary>系统必备功能</summary>
        internal static Dictionary<string, Codon> SystemCodons { get { return sysCodon; } }

        static Dictionary<string, Codon> enableCodon = new Dictionary<string, Codon>(StringComparer.CurrentCultureIgnoreCase);
        internal static Dictionary<string, Codon> EnableCodons { get { return enableCodon; } }

        static LoadAddinMode mode = LoadAddinMode.AddinFile;
        /// <summary>加载功能清单的方式</summary>
        public static LoadAddinMode LoadMode { set { mode = value; } get { return mode; } }
        /// <summary>枚举:加载功能清单的方式</summary>
        public enum LoadAddinMode
        {
            /// <summary>文件</summary>
            AddinFile,
            /// <summary>数据库[文件和数据库均存在,才会加载并显示]</summary>
            Database
        }
        /// <summary>
        /// Gets a dictionary of registered doozers.
        /// </summary>
        public static ConcurrentDictionary<string, IDoozer> Doozers
        {
            get { return doozers; }
        }

        /// <summary>
        /// Checks whether the specified path exists in the AddIn tree.
        /// </summary>
        public static bool ExistsTreeNode(string path)
        {
            return GetTreeNode(path, false) != null;
        }

        /// <summary>
        /// Gets the <see cref="AddInTreeNode"/> representing the specified path.
        /// This method throws a <see cref="CoreException"/> when the
        /// path does not exist.
        /// </summary>
        public static AddInTreeNode GetTreeNode(string path)
        {
            return GetTreeNode(path, true);
        }

        /// <summary>
        /// Gets the <see cref="AddInTreeNode"/> representing the specified path.
        /// </summary>
        /// <param name="path">The path of the AddIn tree node</param>
        /// <param name="throwOnNotFound">
        /// If set to <c>true</c>, this method throws a
        /// <see cref="CoreException"/> when the path does not exist.
        /// If set to <c>false</c>, <c>null</c> is returned for non-existing paths.
        /// </param>
        public static AddInTreeNode GetTreeNode(string path, bool throwOnNotFound)
        {
            if (path == null || path.Length == 0)
            {
                return rootNode;
            }
            string[] splittedPath = path.Split('/');
            AddInTreeNode curPath = rootNode;
            for (int i = 0; i < splittedPath.Length; i++)
            {
                if (!curPath.ChildNodes.TryGetValue(splittedPath[i], out curPath))
                {
                    if (throwOnNotFound)
                        throw new CoreException("Treepath not found: " + path);
                    else
                        return null;
                }
            }
            return curPath;
        }

        /// <summary>
        /// Builds a single item in the addin tree.
        /// </summary>
        /// <param name="path">A path to the item in the addin tree.</param>
        /// <param name="caller">The owner used to create the objects.</param>
        /// <exception cref="CoreException">The path does not
        /// exist or does not point to an item.</exception>
        public static object BuildItem(string path, object caller)
        {
            return BuildItem(path, caller, null);
        }

        public static object BuildItem(string path, object caller, IEnumerable<ICondition> additionalConditions)
        {
            int pos = path.LastIndexOf('/');
            string parent = path.Substring(0, pos);
            string child = path.Substring(pos + 1);
            AddInTreeNode node = GetTreeNode(parent);
            return node.BuildChildItem(child, caller, additionalConditions);
        }

        /// <summary>
        /// Builds the items in the path. Ensures that all items have the type T.
        /// </summary>
        /// <param name="path">A path in the addin tree.</param>
        /// <param name="caller">The owner used to create the objects.</param>
        /// <param name="throwOnNotFound">If true, throws a <see cref="CoreException"/>
        /// if the path is not found. If false, an empty ArrayList is returned when the
        /// path is not found.</param>
        public static List<T> BuildItems<T>(string path, object caller, bool throwOnNotFound)
        {
            AddInTreeNode node = GetTreeNode(path, throwOnNotFound);
            if (node == null)
                return new List<T>();
            else
                return node.BuildChildItems<T>(caller);
        }

        static AddInTreeNode CreatePath(AddInTreeNode localRoot, string path)
        {
            if (path == null || path.Length == 0)
            {
                return localRoot;
            }
            string[] splittedPath = path.Split('/');
            AddInTreeNode curPath = localRoot;
            int i = 0;
            while (i < splittedPath.Length)
            {
                if (!curPath.ChildNodes.ContainsKey(splittedPath[i]))
                {
                    curPath.ChildNodes[splittedPath[i]] = new AddInTreeNode();
                }
                curPath = curPath.ChildNodes[splittedPath[i]];
                ++i;
            }

            return curPath;
        }

        static void AddExtensionPath(ExtensionPath path)
        {
            AddInTreeNode treePath = CreatePath(rootNode, path.Name);
            foreach (IEnumerable<Codon> innerCodons in path.GroupedCodons)
                treePath.AddCodons(innerCodons);
        }

        /// <summary>
        /// The specified AddIn is added to the <see cref="AddIns"/> collection.
        /// If the AddIn is enabled, its doozers, condition evaluators and extension
        /// paths are added to the AddInTree and its resources are added to the
        /// <see cref="ResourceService"/>.
        /// </summary>
        public static void InsertAddIn(AddIn addIn)
        {
            if (addIn.Enabled)
            {
                foreach (ExtensionPath path in addIn.Paths.Values)
                {
                    AddExtensionPath(path);
                }
            }
            addIns.Add(addIn);
        }

        /// <summary>
        /// Loads a list of .addin files, ensuring that dependencies are satisfied.
        /// This method is normally called by <see cref="CoreStartup.RunInitialization"/>.
        /// </summary>
        /// <param name="addInFiles">
        /// The list of .addin file names to load.
        /// </param>
        /// <param name="disabledAddIns">
        /// The list of disabled AddIn identity names.
        /// </param>
        public static void Load(List<string> addInFiles, List<string> disabledAddIns)
        {
            List<AddIn> list = new List<AddIn>();
            Dictionary<string, Version> dict = new Dictionary<string, Version>();
            Dictionary<string, AddIn> addInDict = new Dictionary<string, AddIn>();
            var nameTable = new System.Xml.NameTable();
            foreach (string fileName in addInFiles)
            {
                AddIn addIn;
                try
                {
                    addIn = AddIn.Load(fileName, nameTable);
                }
                catch (CoreException ex)
                {
                    LoggingService.Error(ex);
                    if (ex.InnerException != null)
                    {
                        MessageService.ShowError("Error loading AddIn " + fileName + ":\n"
                                                 + ex.InnerException.Message);
                    }
                    else
                    {
                        MessageService.ShowError("Error loading AddIn " + fileName + ":\n"
                                                 + ex.Message);
                    }
                    addIn = new AddIn();
                    addIn.addInFileName = fileName;
                    addIn.CustomErrorMessage = ex.Message;
                }

                addIn.Enabled = true;
                if (disabledAddIns != null && disabledAddIns.Count > 0)
                {
                    foreach (string name in addIn.Manifest.Identities.Keys)
                    {
                        if (disabledAddIns.Contains(name))
                        {
                            addIn.Enabled = false;
                            break;
                        }
                    }
                }
                if (addIn.Enabled)
                {
                    foreach (KeyValuePair<string, Version> pair in addIn.Manifest.Identities)
                    {
                        if (dict.ContainsKey(pair.Key))
                        {
                            MessageService.ShowError("Name '" + pair.Key + "' is used by " +
                                                     "'" + addInDict[pair.Key].FileName + "' and '" + fileName + "'");
                            addIn.Enabled = false;
                            break;
                        }
                        else
                        {
                            dict.Add(pair.Key, pair.Value);
                            addInDict.Add(pair.Key, addIn);
                        }
                    }
                }
                list.Add(addIn);
            }

            foreach (AddIn addIn in list)
            {
                try
                {
                    InsertAddIn(addIn);
                }
                catch (CoreException ex)
                {
                    LoggingService.Error(ex);
                    MessageService.ShowError("Error loading AddIn " + addIn.FileName + ":\n"
                                             + ex.Message);
                }
            }
        }
    }
}
